iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0

在前面的章節,只有介紹 DX 如何設定 Shader pipeline 跟部份的 Shader 程式,在這篇會更詳細介紹 HLSL 本身的寫法與相關功能。

Shader 是什麼

在硬體的歷史中,CPU 是先出現的,其中組合語言、C、C++ 被創造出來的目的就是用來控制 CPU 相關的計算操作。

在這之後,為了應付日漸複雜的圖學計算,GPU 被創造出來的同時也開始有對應的語言來負責 GPU 的操作。HLSL 就是用來控制 GPU 的語言。

變數

HLSL 宣告變數基本上跟 C++ 相似,為型別 變數名稱;

//Ex:
float3 pos;
pos = float3(1, 1, 1);

常見的如下

型別 說明
bool 布林值,表示 truefalse
int 整數,可以表示正整數、負整數與 0
uint 整數,只能表示 0 或正整數。主要用在索引 ID 這類資料
float 單精度浮點數,表示帶有小數的值
float2 由 2 個 float 組成的向量,例如 (x, y),常用於 UV、2D 座標
float3 由 3 個 float 組成的向量,例如 (x, y, z),常用於位置、方向、法線
float4 由 4 個 float 組成的向量,例如 (x, y, z, w),常用於齊次座標或 RGBA 顏色
float3x3 由 3 × 3 個 float 組成的矩陣,常用於旋轉、法線等 3D 向量的轉換
float4x4 由 4 × 4 個 float 組成的矩陣,常用於 World、View、Projection 等 3D 座標轉換

Semantics

HLSL 除了定義變數的型別,後面還有像這樣的內容

struct VSInput
{
		float3 position : POSITION;
		float2 uv : TEXCOORD0;
};

struct VSOutput
{
		float4 position : SV_POSITION;
};

Semantic 是用來告訴 Shader Compiler 這個資料要如何使用,常見的如下

名字 說明
POSITION 物件空間的頂點座標
COLOR 頂點顏色
TEXCOORD 基本用法為 UV 座標

其中 TEXCOORD 特別說基本用法為 UV 的原因,其實是因為他不限於 UV 座標,他可以是當成是一種泛用 Shader Compiler 可以理解的資料格式,像是以下的例子 Compiler 都可以理解的

struct VS_OUT
{
		float4 position : SV_POSITION;
		float2 uv : TEXCOORD0;
		float3 normal : TEXCOORD1;
		float3 worldPos : TEXCOORD2;
		float3 color : TEXCOORD3;
};

而前綴 SV_ 是告訴資料最後要輸出出去

//Vertex Shader 要出最後的頂點位置供 Rasterizer 使用
struct VSOutput
{
		float4 position : SV_POSITION;
};

Shader Register

接下來的範例會常看到類似這樣的設定

cbuffer CameraData : register(b0)
{
		float4x4 viewProjection;
};

RWStructuredBuffer<float4> outputBuffer : register(u0);

這設定是因為 CPU 與 GPU 使用不同的記憶體位址空間,需要讓 C++ 在 CPU 設定的值有辦法傳遞到 GPU,而 Graphics API 會把 GPU 資源綁定在 Shader 能辨識的 Slot (位址)裡,而 register() 就是在指定這個資源要使用哪個 Slot。

而對應的資源前綴如下

b Constant Buffer
t Texture
c Buffer offset
s Sampler
u Unordered Access View

數學

saturate()

將數值限制在 0 ~ 1 的範圍

saturate(-1.0f); // 0 
saturate(0.5f); // 0.5 
saturate(2.0f); // 1

clamp()

將數值限制在指定的最小值與最大值之間

clamp(-1.0f, 0.0f, 1.0f); // 0 
clamp(0.5f, 0.0f, 1.0f); // 0.5 
clamp(2.0f, 0.0f, 1.0f); // 1

min()

比較兩個值,回傳較小的值

min(3.0f, 5.0f); // 3
min(8.0f, 2.0f); // 2

max()

比較兩個值,回傳較大的值

max(3.0f, 5.0f); // 5
max(8.0f, 2.0f); // 8

abs()

取得數值的絕對值

abs(-5.0f); // 5
abs(5.0f);  // 5
abs(0.0f);  // 0

floor()

將數值向下取整

floor(1.2f);  // 1
floor(1.9f);  // 1
floor(-1.2f); // -2

ceil()

將數值向上取整

ceil(1.2f);  // 2
ceil(1.9f);  // 2
ceil(-1.2f); // -1

round()

將數值四捨五入到最接近的整數

round(1.2f); // 1
round(1.6f); // 2
round(2.5f); // 2

frac()

只取得數值的小數部分

frac(1.25f); // 0.25
frac(3.75f); // 0.75
frac(5.0f);  // 0

sqrt()

計算平方根

sqrt(4.0f);  // 2
sqrt(9.0f);  // 3
sqrt(25.0f); // 5

pow()

計算次方。

pow(2.0f, 2.0f); // 4
pow(2.0f, 3.0f); // 8
pow(3.0f, 2.0f); // 9

sin()

計算正弦值。

sin(0.0f); // 0

cos()

計算餘弦值。

cos(0.0f); // 1

sin() 與 cos() 常用在旋轉、波浪、週期運動這類的視覺表現


length()

計算 Vector 的長度。

float3 value = float3(3, 4, 0);

float len = length(value); // 5

distance()

計算兩個點之間的距離。

float3 a = float3(0, 0, 0);
float3 b = float3(3, 4, 0);

float d = distance(a, b); // 5

normalize()

將向量標準化,使它的長度變成 1。用於拿到向量方向後方便計算。

float3 direction = float3(10, 0, 0);

float3 result = normalize(direction);
// (1, 0, 0)

dot()

計算兩個 Vector 的內積

float3 a = float3(1, 0, 0);
float3 b = float3(1, 0, 0);

float result = dot(a, b); // 1

如果兩個 Vector 都已經標準化的,就可以從此數值得知兩向量彼此方向的對應關係

dot = 1  → 完全同方向
dot = 0  → 約 90 度
dot = -1 → 完全反方向

cross()

計算兩個 Vector 的外積,結果會得到一個同時垂直於兩者的新 Vector

float3 xAxis = float3(1, 0, 0);
float3 yAxis = float3(0, 1, 0);

float3 result = cross(xAxis, yAxis); // (0, 0, 1)

圖為這樣

                yAxis
              (0, 1, 0)
                  ↑
                  │
                  │
                  ●──────── → xAxis
                 /          (1, 0, 0)
                /
               ↗
        result = (0, 0, 1)
              Z Axis

lerp()

在兩個數值之間進行線性插值。

lerp(0.0f, 10.0f, 0.0f); // 0
lerp(0.0f, 10.0f, 0.5f); // 5
lerp(0.0f, 10.0f, 1.0f); // 10

step()

根據指定的臨界值,回傳 0 或 1

//step(edge, x);  x < edge  → 0  x >= edge → 1
step(0.5f, 0.2f); // 0
step(0.5f, 0.5f); // 1
step(0.5f, 0.8f); // 1

smoothstep()

和 step() 類似,但會在兩個範圍之間平滑地從 0 過渡到 1

//smoothstep(min, max, value);
smoothstep(0.0f, 1.0f, 0.0f); // 0
smoothstep(0.0f, 1.0f, 0.5f); // 約 0.5
smoothstep(0.0f, 1.0f, 1.0f); // 1

mul()

進行 Vector 與 Matrix,或 Matrix 與 Matrix 的乘法

float4 worldPosition =
    mul(
        float4(position, 1.0f),
        worldMatrix
    );

在 HLSL 中常用於座標轉換

Local Space
    ↓ World Matrix
World Space
    ↓ View Matrix
View Space
    ↓ Projection Matrix
Clip Space

當然實際上 HLSL 還有更多功能,剩下的會在接下來的章節中介紹出來

參考資料

HLSL シェーダーの魔導書 シェーディングの基礎からレイトレーシングまで

register - Win32 apps


上一篇
Day 10:Graphics Pipeline 介紹(6) - 輸出三角形
下一篇
Day 12:StructuredBuffer
系列文
因為 AI 看不懂老舊程式,只好乖乖從零開始學 DirectX 12 與 HLSL13
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言